package client

import (
	"context"
	"sync"

	"google.golang.org/protobuf/proto"

	"gitee.com/hasika/common-server/conn/inet"
	"gitee.com/hasika/gotool/errorx"
)

const (
	ConnectionNil errorx.ConstStringErr = "connection nil"
)

type Session[T Req, H Rsp] struct {
	sync.RWMutex
	connectionMap map[inet.ConnectionType]*Client[T, H]
	option        *Option[T, H]
	ctx           context.Context
}

func (s *Session[T, H]) CallBodyWithCallback(connectionType inet.ConnectionType, message proto.Message, callback func(message proto.Message, e error)) error {
	cl := s.selectConnection(connectionType)
	if cl == nil {
		return ConnectionNil
	}
	return cl.CallBodyWithCallback(message, callback)
}

func (s *Session[T, H]) CallBodyAndWait(connectionType inet.ConnectionType, message proto.Message) (proto.Message, error) {
	cl := s.selectConnection(connectionType)
	if cl == nil {
		return nil, ConnectionNil
	}
	return cl.CallBodyAndWait(message)
}

func (s *Session[T, H]) CallBodyOnly(connectionType inet.ConnectionType, message proto.Message) error {
	cl := s.selectConnection(connectionType)
	if cl == nil {
		return ConnectionNil
	}
	return cl.CallBodyOnly(message)
}

func (s *Session[T, H]) ConnectionMap() map[inet.ConnectionType]*Client[T, H] {
	return s.connectionMap
}

func (s *Session[T, H]) start() error {
	for _, dial := range s.option.GetDialOptions() {
		cl, err := NewClient[T, H](s.ctx, s.option, dial)
		if err != nil {
			return err
		}
		s.connectionMap[cl.Conn().GetConnectionType()] = cl
	}
	return nil
}

func (s *Session[T, H]) GetClient(connectionType inet.ConnectionType) *Client[T, H] {
	s.RLock()
	defer s.RUnlock()
	cl := s.connectionMap[connectionType]
	return cl
}

func (s *Session[T, H]) selectConnection(connectionType inet.ConnectionType) *Client[T, H] {
	cl := s.GetClient(connectionType)
	if cl == nil {
		return s.GetClient(inet.Tcp)
	}
	return cl
}

func NewSession[T Req, H Rsp](ctx context.Context, option *Option[T, H]) (*Session[T, H], error) {
	s := &Session[T, H]{
		ctx:           ctx,
		connectionMap: make(map[inet.ConnectionType]*Client[T, H]),
		option:        option,
	}
	err := s.start()
	if err != nil {
		return nil, err
	}
	return s, nil
}
